package org.hackystat.projecttrajectory.resource;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClientException;
import org.hackystat.dailyprojectdata.resource.codeissue.jaxb.CodeIssueDailyProjectData;
import org.hackystat.dailyprojectdata.resource.codeissue.jaxb.CodeIssueData;
import org.hackystat.dailyprojectdata.resource.filemetric.jaxb.FileMetricDailyProjectData;
import org.hackystat.projecttrajectory.resource.summary.jaxb.BuildData; 
import org.hackystat.projecttrajectory.resource.summary.jaxb.CodeIssueSummary;
import org.hackystat.projecttrajectory.resource.summary.jaxb.ProjectData;
import org.hackystat.projecttrajectory.resource.summary.jaxb.ProjectSummary;
import org.hackystat.projecttrajectory.resource.trajectory.jaxb.ProjectTrajectory;
import org.hackystat.projecttrajectory.resource.trajectory.jaxb.ProjectTrajectoryData;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.utilities.time.period.Day;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Creates ProjectSummaries from the given Sensorbase and DailyProjectData.
 * 
 * @author Cam Moore
 * 
 */
public class ProjectSummaryFactory {

  private SensorBaseClient sbClient;
  private DailyProjectDataClient dpdClient;
  private String user;

  /**
   * Constructs a ProjectSummaryFactory.
   * 
   * @param user a {@link String}, the user.
   * @param sbClient the {@link SensorBaseClient} to use.
   * @param dpdClient the {@link DailyProjectDataClient} to use.
   */
  public ProjectSummaryFactory(String user, SensorBaseClient sbClient,
      DailyProjectDataClient dpdClient) {
    this.user = user;
    this.sbClient = sbClient;
    this.dpdClient = dpdClient;
  }

  /**
   * @param projectName a {@link String}, the name of the project to summarize.
   * @param date a {@link XMLGregorianCalendar}, the date of the summary.
   * @return The {@link ProjectSummary} for the given name on the given date.
   */
  public ProjectSummary getSummary(String projectName, XMLGregorianCalendar date) {
    XMLGregorianCalendar earlyDate = date;
    ProjectSummary summary = new ProjectSummary();
    try {
      Project project = getProject(projectName);
      if (project != null) {
        if (Tstamp.greaterThan(earlyDate, project.getEndTime())) {
          earlyDate = project.getEndTime();
        }
        fillSummary(project, earlyDate, summary);
      }
    } catch (SensorBaseClientException e) {
      e.printStackTrace();
    }

    return summary;
  }

  /**
   * @param projectName a {@link String}, the name of the project.
   * @param date a {@link XMLGregorianCalendar}, the date to calculate the
   *          trajectory to.
   * @return The {@link ProjectTrajectory} for the given project name and date.
   */
  public ProjectTrajectory getTrajectory(String projectName, XMLGregorianCalendar date) {
    XMLGregorianCalendar earlyDate = date;
    ProjectTrajectory trajectory = new ProjectTrajectory();
    try {
      Project project = getProject(projectName);
      if (project != null) {
        if (Tstamp.greaterThan(earlyDate, project.getEndTime())) {
          earlyDate = project.getEndTime();
        }
        fillTrajectory(project, earlyDate, trajectory);
      }
    } catch (SensorBaseClientException e) {
      e.printStackTrace();
    }
    return trajectory;
  }

  /**
   * Fills the given ProjectSummary.
   * 
   * @param project a Project.
   * @param date a XMLGregorianCalendar the date.
   * @param summary a ProjectSummary.
   */
  private void fillSummary(Project project, XMLGregorianCalendar date, ProjectSummary summary) {
    summary.setOwner(user);
    summary.setProject(project.getName());
    summary.setDate(date);
    summary.setProjectData(buildProjectData(project, date));
    BuildData bd = buildBuildData(project, date);
    if (bd != null && (bd.getFailures().intValue() > 0 || bd.getSuccesses().intValue() > 0)) {
      summary.setBuildData(bd);
    }
    CodeIssueSummary cis = buildCodeIssueData(project, date);
    if (cis != null && cis.getCodeIssueData().size() > 0) {
      summary.setCodeIssueSummary(cis);
    }
  }

  /**
   * Fills the given ProjectTrajectory.
   * 
   * @param project a {@link Project}, the project to use.
   * @param date a {@link XMLGregorianCalendar}.
   * @param trajectory a {@link ProjectTrajectory}.
   */
  private void fillTrajectory(Project project, XMLGregorianCalendar date,
      ProjectTrajectory trajectory) {
    trajectory.setOwner(user);
    trajectory.setProject(project.getName());
    trajectory.setDate(date);
    List<ProjectTrajectoryData> dataList = trajectory.getProjectTrajectoryData();
    try {
      XMLGregorianCalendar tempDate = startDate(project);
      long totalEffort = 0;
      long dailyEffort = 0;
      // build the trajectories for the past active days.
      while (Tstamp.lessThan(tempDate, date)) {
        dailyEffort = dpdClient.getDevTime(user, project.getName(), tempDate).getTotalDevTime()
            .longValue();
        if (dailyEffort > 0) {
          ProjectTrajectoryData data = new ProjectTrajectoryData();
          data.setTotalSize(BigInteger.valueOf(getProjectSize(project, tempDate)));
          data.setDate(tempDate);
          totalEffort += dailyEffort;
          data.setTotalEffort(BigInteger.valueOf(totalEffort));
          dataList.add(data);
        }
        // increment the date
        tempDate = Tstamp.incrementDays(tempDate, 1);
      }
      // get the last day's data
      dailyEffort = dpdClient.getDevTime(user, project.getName(), tempDate).getTotalDevTime()
          .longValue();
      if (dailyEffort > 0) {
        ProjectTrajectoryData data = new ProjectTrajectoryData();
        data.setTotalSize(BigInteger.valueOf(getProjectSize(project, tempDate)));
        data.setDate(tempDate);
        totalEffort += dailyEffort;
        data.setTotalEffort(BigInteger.valueOf(totalEffort));
        dataList.add(data);
      }
    } catch (SensorBaseClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   * Builds a ProjectData instance, the project status at the given date.
   * 
   * @param project a Project, the project to get the status.
   * @param date a XMLGregorianCalendar.
   * @return A ProjectData instance.
   */
  private ProjectData buildProjectData(Project project, XMLGregorianCalendar date) {
    ProjectData data = new ProjectData();
    try {
      data.setTotalSize(BigInteger.valueOf(getProjectSize(project, date)));
      long totalEffort = 0;
      long activeDays = 0;
      // start on first day with sensor data
      XMLGregorianCalendar tempTime = startDate(project);
      if (Tstamp.greaterThan(tempTime, date)) {
        // the project hasn't started yet.
        data.setTotalEffort(BigInteger.valueOf(totalEffort));
        data.setActiveDays(BigInteger.valueOf(activeDays));
      } else {
        // Add up all the effort and find the active days.
        long dailyEffort;
        while (Tstamp.greaterThan(date, tempTime)) {
          dailyEffort = dpdClient.getDevTime(user, project.getName(), tempTime).getTotalDevTime()
              .longValue();
          totalEffort += dailyEffort;
          if (dailyEffort > 0) {
            activeDays++;
          }
          // increment the date
          tempTime = Tstamp.incrementDays(tempTime, 1);
        }
        // get last day's data
        dailyEffort = dpdClient.getDevTime(user, project.getName(), tempTime).getTotalDevTime()
            .longValue();
        totalEffort += dailyEffort;
        if (dailyEffort > 0) {
          activeDays++;
        }
        data.setActiveDays(BigInteger.valueOf(activeDays));
        data.setTotalEffort(BigInteger.valueOf(totalEffort));

      }
    } catch (SensorBaseClientException e) {
      e.printStackTrace();
    } catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return data;
  }

  /**
   * Builds a BuildData instance, the summary of build information at the given
   * date.
   * 
   * @param project a Project, the project to get the status.
   * @param date a XMLGregorianCalendar.
   * @return A ProjectData instance.
   */
  private BuildData buildBuildData(Project project, XMLGregorianCalendar date) {
    BuildData data = new BuildData();
    try {
      long successes = 0;
      long failures = 0;
      // start on first day with sensor data
      XMLGregorianCalendar tempTime = startDate(project);
      if (Tstamp.greaterThan(tempTime, date)) {
        // the project hasn't started yet.
        data.setFailures(BigInteger.ZERO);
        data.setSuccesses(BigInteger.ZERO);
      } else {
        while (Tstamp.greaterThan(date, tempTime)) {
          // Add up all the build successes and failures
          for (org.hackystat.dailyprojectdata.resource.build.jaxb.MemberData mBData : dpdClient
              .getBuild(user, project.getName(), tempTime).getMemberData()) {
            successes += mBData.getSuccess();
            failures += mBData.getFailure();
          }
          // increment the date
          tempTime = Tstamp.incrementDays(tempTime, 1);
        }
        data.setSuccesses(BigInteger.valueOf(successes));
        data.setFailures(BigInteger.valueOf(failures));
      }
    } catch (SensorBaseClientException e) {
      e.printStackTrace();
    } catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return data;
  }

  /**
   * Builds a BuildData instance, the summary of build information at the given
   * date.
   * 
   * @param project a Project, the project to get the status.
   * @param date a XMLGregorianCalendar.
   * @return A ProjectData instance.
   */
  private CodeIssueSummary buildCodeIssueData(Project project, XMLGregorianCalendar date) {
    CodeIssueSummary summary = new CodeIssueSummary();
    Map<String, Integer> issueCounts = new HashMap<String, Integer>();
    try {
      // start on first day with sensor data
      XMLGregorianCalendar tempTime = startDate(project);
      if (!Tstamp.greaterThan(tempTime, date)) {
        Integer issueCount;
        while (Tstamp.greaterThan(date, tempTime)) {
          CodeIssueDailyProjectData ci = dpdClient.getCodeIssue(user, project.getName(), tempTime,
              null, null);
          for (CodeIssueData d : ci.getCodeIssueData()) {
            issueCount = issueCounts.get(d.getIssueType());
            if (issueCount == null) {
              issueCount = Integer.valueOf(0);
            }
            issueCount += d.getNumIssues();
            issueCounts.put(d.getIssueType(), issueCount);
          }
          // increment the date
          tempTime = Tstamp.incrementDays(tempTime, 1);
        }
      }
      for (Map.Entry<String, Integer> entry : issueCounts.entrySet()) {
        org.hackystat.projecttrajectory.resource.summary.jaxb.CodeIssueData codeData = 
          new org.hackystat.projecttrajectory.resource.summary.jaxb.CodeIssueData();
        codeData.setIssueType(entry.getKey());
        codeData.setNumIssues(entry.getValue().intValue());
        summary.getCodeIssueData().add(codeData);
      }
    } catch (SensorBaseClientException e) {
      e.printStackTrace();
    } catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return summary;
  }

  /**
   * @param projectName a String the name of the project.
   * @return The Project instance for the given name.
   * @throws SensorBaseClientException If there is a problem getting the
   *           project.
   */
  public Project getProject(String projectName) throws SensorBaseClientException {
    return sbClient.getProject(user, projectName);
  }

  /**
   * Returns the timestamp of the first Day that sensordata was collected for
   * the given project.
   * 
   * @param project a Project.
   * @return The XMLGregorianCalendar representing the time of the first sensor
   *         data entry.
   * @throws SensorBaseClientException if there is a problem with the sensorbase
   *           client.
   */
  private XMLGregorianCalendar startDate(Project project) throws SensorBaseClientException {
    // find first day of data
    SensorDataIndex index = sbClient.getProjectSensorData(user, project.getName());
    List<SensorDataRef> data = index.getSensorDataRef();
    if (!data.isEmpty()) {
      SensorData firstEntry = sbClient.getSensorData(data.get(0));
      XMLGregorianCalendar dataTime = firstEntry.getTimestamp();
      // now convert to uniform starting time
      Day day = Day.getInstance(dataTime.getYear(), dataTime.getMonth() - 1, dataTime.getDay());
      return Tstamp.makeTimestamp(day);
    }
    // There is not data so return 'now'
    return Tstamp.makeTimestamp();
  }

  /**
   * @param project a Project.
   * @param date a XMLGregorianCalendar, the date.
   * @return The size of the project on the given date.
   */
  private long getProjectSize(Project project, XMLGregorianCalendar date) {
    // long startSize = 0;
    long size = 0;
    // find first day of data
    XMLGregorianCalendar dataStartTime;
    try {
      dataStartTime = startDate(project);
      // get the size at the start of the project
      // FileMetricDailyProjectData startData = dpdClient.getFileMetric(user,
      // project.getName(),
      // dataStartTime, "TotalLines");
      // if (startData.getTotal() > 0) {
      // startSize = Math.round(startData.getTotal());
      // }
      // get total lines
      FileMetricDailyProjectData fileData = dpdClient.getFileMetric(user, project.getName(), date,
          "TotalLines");
      if (fileData.getTotal() > 0) {
        size = Math.round(fileData.getTotal());
      } else {
        // look backwards by day until we find a size or we get to the start day
        // of the project.
        XMLGregorianCalendar tempTime = date;
        while (size == 0 && Tstamp.greaterThan(tempTime, dataStartTime)) {
          // decrement tempTime by one day
          tempTime = Tstamp.incrementDays(tempTime, -1);
          size = Math.round(dpdClient
              .getFileMetric(user, project.getName(), tempTime, "TotalLines").getTotal());
        }
      }
    } catch (SensorBaseClientException e) {
      e.printStackTrace();
    } catch (DailyProjectDataClientException e) {
      e.printStackTrace();
    }

    return size; // should we remove the starting size?
  }
}
